﻿/*
 * Below is sample script
 * 
 * =====================================================================
 * References:
 * http://blog.csdn.net/Donjuan/article/details/3865026
 * 
 * =====================================================================
 * 
<root Name="Test" ResultFolder="G:\C#_Projects\ScreenTester\_Results">
	<OperationBlock>
    <Keyboard Value="VK_LWIN,VK_R"/>
    <Separator />
    <InputText Value="iexplore.exe www.google.com"></InputText>
    <Keyboard Value="VK_RETURN" />
    <Separator WaitSeconds="2"/>
    <InputText Value="Hello, World" />
    <Separator />
    <Keyboard Value="VK_RETURN" />
  </OperationBlock>
  <OperationBlock>
    <Mouse Value="MOUSEEVENTF_MOVE,MOUSEEVENTF_ABSOLUTE" X="1000" Y="500" />
  </OperationBlock>
</root>
 */


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Drawing;
using System.IO;
using ScreenTester.Exceptions;

namespace ScreenTester
{
    class ExecutiveOperationBlock
    {
        public ExecutiveOperation[] OperCollection;
    }

    public class SimulationOperationExecuter
    {
        #region Constants
        /// <summary>
        /// Test cases file extension
        /// </summary>
        public static readonly string EXTENSION = "scrntst";
        /// <summary>
        /// Node: 'Root'name of the root
        /// </summary>
        public static readonly string XML_ROOT = "Root";
        public static readonly string XML_VALUE = "Value";

        /// <summary>
        /// Attribute: name of the test case
        /// </summary>
        public static readonly string XML_NAME = "Name";
        /// <summary>
        /// Attribute: 'ResultFolder'folder path of the results
        /// </summary>
        public static readonly string XML_RESULT_FOLDER = "ResultFolder";
        /// <summary>
        /// Node: 'OperationBlock', Operation block. And the end of the block, there will be a snapshot
        /// </summary>
        public static readonly string XML_OPERATION_BLOCK = "OperationBlock";
        /// <summary>
        /// Node: 'Keyboard' event. 
        ///     parameter: 'Value' 
        /// </summary>
        public static readonly string XML_KEYBOARD = "Keyboard";
        /// <summary>
        /// Node: Input text.
        ///     parameter: 'Value'.
        /// </summary>
        public static readonly string XML_TEXT = "InputText";
        /// <summary>
        /// Node: 'Separator' between two operations
        ///     parameter: 'WaitSeconds'
        /// </summary>
        public static readonly string XML_SEPARATOR = "Separator";
        public static readonly string XML_WAITING_SECONDES = "WaitSeconds";

        /// <summary>
        /// Node: mouse event
        ///     parameter: 'Value'. mouse event
        ///     parameter: 'X' for x aix
        ///     parameter: 'Y' for y aix
        /// </summary>
        public static readonly string XML_MOUSE = "Mouse";
        public static readonly string XML_X = "X";
        public static readonly string XML_Y = "Y";

        /// <summary>
        /// Node: Focus on windows
        ///     parameter: 'ApplicationName' for application name.
        ///     parameter: 'ApplicationTitle' for application title.
        ///     parameter: 'Width' for width of the screen size
        ///     parameter: 'Height' for height of the screen size
        /// </summary>
        public static readonly string XML_FORCUS_WINDOWS = "FocusWindows";
        public static readonly string XML_APPLICATION_NAME = "ApplicationName";
        public static readonly string XML_APPLICATION_TITLE = "ApplicationTitle";
        public static readonly string XML_WIDTH = "Width";
        public static readonly string XML_HEIGHT = "Height";


        #endregion

        #region fields

        private List<ExecutiveOperationBlock> _blocks;
        private string _configFilePath;
        private string _name;
        private string _resultFolderPath;
        private int _interval;
        #endregion

        /// <summary>
        /// Simulation Operation Executer
        /// </summary>
        /// <param name="configFilePath"></param>
        /// <param name="interval"></param>
        public SimulationOperationExecuter(string configFilePath
            , int interval)
        {
            _configFilePath = configFilePath;
            Console.WriteLine("Test script file: {0}", _configFilePath);

            XDocument xdoc = XDocument.Load(_configFilePath);
            XElement xelem = xdoc.Root;

            _name = xelem.Attribute(XML_NAME).Value;
            _resultFolderPath = xelem.Attribute(XML_RESULT_FOLDER).Value;

            _resultFolderPath = string.Format("{0}\\{1}_{2}"
                , _resultFolderPath
                , _name
                , DateTime.Now.ToFileTimeUtc());
            Console.WriteLine("Test name: {0}, result folder: {1}"
                , _name
                , _resultFolderPath);

            try
            {
                generateBlocks(xelem);
            }
            catch(Exception e)
            {
                throw new Exception(string.Format("Configure file format error, /nDeep Exception: {0}{1}"
                    , e.Message
                    , e.StackTrace));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xblock"></param>
        private void generateBlocks(XElement xroot)
        {
            _blocks = new List<ExecutiveOperationBlock>();
            foreach (XElement xblock in xroot.Elements())
            {
                List<ExecutiveOperation> operations = new List<ExecutiveOperation>();
                foreach (XElement xoper in xblock.Elements())
                {
                    if (xoper.Name.LocalName.Equals(XML_KEYBOARD))
                    {
                        generateKeyboards(xoper, operations);
                    }
                    else if (xoper.Name.LocalName.Equals(XML_TEXT))
                    {
                        generateText(xoper, operations);
                    }
                    else if (xoper.Name.LocalName.Equals(XML_SEPARATOR))
                    {
                        generateText(xoper, operations);
                    }
                    else if (xoper.Name.LocalName.Equals(XML_MOUSE))
                    {
                        generateMouse(xoper, operations);
                    }
                    else if (xoper.Name.LocalName.Equals(XML_FORCUS_WINDOWS))
                    {
                        generateApplication(xoper, operations);
                    }

                }
                _blocks.Add(new ExecutiveOperationBlock()
                {
                    OperCollection = operations.ToArray<ExecutiveOperation>()
                });
            }
        }

        /// <summary>
        /// Parse xml to keyboard operation
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private void generateKeyboards(XElement xelem, List<ExecutiveOperation> operations)
        {
            // parse keys value, get the bination
            List<Keyboard> keys = new List<Keyboard>();
            XAttribute attr = xelem.Attribute(XML_VALUE);
            if (attr == null)
            {
                throw new FileFormatException("Keyboard should contain the attribute called 'Value'");
            }
            string value = attr.Value;
            string[] tokens = value.Split(',');

            foreach (string str in tokens)
            {
                Keyboard key = (Keyboard)Enum.Parse(typeof(Keyboard), str);
                keys.Add(key);
            }

            operations.Add(new KeyboardOperation(keys.ToArray<Keyboard>()));
        }

        /// <summary>
        /// Parse xml to mouse operation
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private void generateMouse(XElement xelem, List<ExecutiveOperation> operations)
        {
            // create a new single operator
            // parse keys value, get the bination
            List<MouseEventFlag> keys = new List<MouseEventFlag>();
            XAttribute attr = xelem.Attribute(XML_VALUE);
            if (attr == null)
            {
                throw new FileFormatException("MouseEvent block should contain attribute 'Value'.");
            }
            string value = attr.Value;
            string[] tokens = value.Split(',');


            foreach (string str in tokens)
            {
                MouseEventFlag key = (MouseEventFlag)Enum.Parse(typeof(MouseEventFlag), str);
                keys.Add(key);
            }

            XAttribute attrX = xelem.Attribute(XML_X);
            XAttribute attrY = xelem.Attribute(XML_Y);
            if (attrX == null && attrY == null)
            {
                operations.Add(new MouseOperation(keys.ToArray<MouseEventFlag>()));
                return;
            }
            if (attrX == null || attrY == null)
            {
                throw new FileFormatException("MouseEvent block should contain both X and Y attributes.");
            }
            try
            {
                int x = Convert.ToInt32(attrX);
                int y = Convert.ToInt32(attrY);

                if (x <= 0 || y <= 0)
                {
                    throw new FileFormatException("X or Y is not positive number.");
                }
                operations.Add(new MouseOperation(x, y,
                    keys.ToArray<MouseEventFlag>()));
            }
            catch (Exception e)
            {
                throw new FileFormatException(string.Format(
@"X or Y is not positive number.
-------------------------------------------
{0}",
                     e.ToString()));
            }

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private void generateText(XElement xelem, List<ExecutiveOperation> operations)
        {
            // create a new single operator
            XAttribute attr = xelem.Attribute(XML_NAME);
            if (attr == null)
            {
                throw new FileFormatException("Text block should contain attribute 'Value'.");
            }
            // parse keys value, get the bination
            operations.Add(new TextInputOperation(attr.Value));
        }

        /// <summary>
        /// Generate Seperator
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private void generateSeperator(XElement xelem, List<ExecutiveOperation> operations)
        {
            int waitingTime = -1;
            XAttribute attr = xelem.Attribute(XML_WAITING_SECONDES);
            if (attr == null)
            {
                throw new FileFormatException("Seperator block should contain 'WaitSeconds' attribute.");
            }
            try
            {
                waitingTime = Convert.ToInt32(attr.Value) * 1000;
                if (waitingTime <= 0)
                {
                    throw new FileFormatException("WaitSeconds should be positive number");
                }
                operations.Add(new SeparatorOperation(waitingTime));
            }
            catch (Exception e)
            {
                throw new FileFormatException(string.Format(
@"WaitSeconds should be positive number
---------------------------------
{0}"
                    , e.ToString()));
            }
        }
        /// <summary>
        /// Generate Application operation
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private void generateApplication(XElement xelem, List<ExecutiveOperation> operations)
        {
            // create a new single operator
            // parse keys value, get the bination
            XAttribute attrName = xelem.Attribute(XML_APPLICATION_NAME);
            if (attrName == null)
            {
                throw new FileFormatException("Application block should contain attribute 'ApplicationName'.");
            }

            XAttribute attrTitle = xelem.Attribute(XML_APPLICATION_TITLE);
            if (attrTitle == null)
            {
                throw new FileFormatException("Application block should contain attribute 'ApplicationTitle'.");
            }

            XAttribute attrWidth = xelem.Attribute(XML_WIDTH);
            XAttribute attrHeight = xelem.Attribute(XML_HEIGHT);

            if (attrWidth == null || attrHeight == null)
            {
                throw new FileFormatException("Application operation block should contain both width and height attributes.");
            }
            try
            {
                int width = Convert.ToInt32(attrWidth);
                int height = Convert.ToInt32(attrHeight);

                if (width <= 0 || height <= 0)
                {
                    throw new FileFormatException("Width or Height is not positive number.");
                }
                operations.Add(new ApplicationOperation(attrName.Value, attrTitle.Value, width, height));
            }
            catch (Exception e)
            {
                throw new FileFormatException(string.Format(
@"Width or Height is not positive number.
-------------------------------------------
{0}",
                     e.ToString()));
            }

        }

        public void Execute()
        {
            // generate result folder
            Directory.CreateDirectory(_resultFolderPath);
            int blockCount = 0;

            Controller controller = Controller.GetInstance(_interval);
            foreach (ExecutiveOperationBlock block in _blocks)
            {
                int picCound = 1;
                Bitmap[] bmps = controller.ExecuteOperationList(block.OperCollection);
                foreach (Bitmap bmp in bmps)
                {
                    string fileName = string.Format("{0}\\{1}_{2}.bmp"
                        , _resultFolderPath
                        , blockCount
                        , picCound);
                    bmp.Save(fileName);

                    picCound++;
                }
                blockCount++;

            }
        }
    }
}
